import re
import random
import sys
import time
from datetime import datetime
import json
import requests
import pytz
import tabulate
import pandas as pd
from BarkNotificator import BarkNotificator
import os
import schedule
import copy

tabulate.PRESERVE_WHITESPACE = True
tabulate.WIDE_CHARS_MODE = True

over_buy = 70
over_sell = 30

# build:
# .venv/bin/pyinstaller --onefile ./mjr_watcher.py


def format_time(timestamp):
    tz = pytz.timezone("Asia/Shanghai")
    return datetime.fromtimestamp(timestamp, tz).strftime("%Y-%m-%d %H:%M:%S")


def get_timestamp(time_str):
    # time_str = "2024-03-04 16:40:56"
    struct_time = time.strptime(time_str, "%Y-%m-%d %H:%M:%S")
    timestamp = time.mktime(struct_time)  # 输出浮点数时间戳
    return timestamp


re_chinese = re.compile(
    r"[\u4e00-\u9fa5\！\？\。\＂\＇\（\）\＊\＋\，\－\／\：\；\＜\＝\＞\＠\［\＼\］\＾\＿\｀\｛\｜\｝\～\｟\｠\、\〃\《\》\「\」\『\』\【\】\〔\〕\〖\〗\〘\〙\〚\〛\〜\〝\〞\〟\〰\〾\〿\–\—\‘\’\‛\“\”\„\‟\…\‧\﹏\．]", re.S)


def format_ascii(text):
    t = re.findall(re_chinese, text)
    count = len(t)
    return text + " " * count + u"\u3000" * (len(text) - count)


def long_profit(open_at, now):
    return now / open_at - 1


# 做空
def short_profit(open_at, now):
    return 1 - now / open_at


class MjrWatcher:
    # class attribute
    curr_t = {}  # 当前周期节点 map interval:fetch_ts
    datas = {}  # map interval:data
    out = {}

    stress = {
        "s1": [107011.0, "0%"],
        "s2": [104722.0, "0%"],
        "s3": [102839.0, "0%"],
        "s4": [99242.0, "0%"],
        "s5": [97500.0, "0%"],
        "s6": [95831.0, "0%"],
        "s7": [94114.0, "0%"],
        "s8": [92565.0, "0%"],
        "s9": [90972.0, "0%"],
        "s10": [89159.0, "0%"],
        "s11": [87810.0, "0%"],
        "s12": [86285.0, "0%"],
        "s13": [84496.0, "0%"],
        "s14": [82281.0, "0%"],
        "s15": [80677.0, "0%"],
        "s16": [78929.0, "0%"],
        "s17": [77377.0, "0%"],
        "s18": [75673.0, "0%"],
        "s19": [74316.0, "0%"],
        "s20": [72946.0, "0%"],
        "s21": [71630.0, "0%"],
        "s22": [70402.0, "0%"],
        "s23": [68691.0, "0%"],
        "s24": [67376.0, "0%"],
    }
    # instance attribute

    def __init__(self, api_keys, push_keys, symbol="BTC/USTD", intervals=["4h", "1h", "30m"], real_time=False):
        self.symbol = symbol
        self.api_keys = api_keys
        self.push_key = push_keys
        self.lazy = False
        self.intervals = intervals
        self.real_time = real_time

        for item in intervals:
            self.curr_t[item] = ""

        # self.get_data()

    def write_cache_data(self, interval, data):
        path = f"{os.getcwd()}/taapi_rsp_{interval}.json"
        with open(path, "w", encoding="utf-8") as file:
            file.write(json.dumps(data))
        return

    def read_cache_data(self, interval):
        err = ""
        path = f"{os.getcwd()}/taapi_rsp_{interval}.json"
        try:
            # 以只读模式打开文件
            with open(path, "r", encoding="utf-8") as file:
                content = file.read()
                data = json.loads(content)
                print(f"从文件 {path} 中读取到的内容是: {data['time']}")
                self.curr_t[interval] = data["time"]
                self.datas[interval] = data
            return
        except FileNotFoundError:
            err = f"文件 {path} 未找到。"
        except Exception as e:
            print(f"读取文件时出现错误: {e}")

        if err != "":
            self.fetch_api_data(interval)
            print(err)

    def get_curr_cycle(self, interval):
        cycle1w = 7 * 24 * 3600
        cycle1d = 24 * 3600
        cycle4h = 4 * 3600
        cycle1h = 1 * 3600
        cycle30m = 30 * 60
        cycle15m = 15 * 60

        nt = time.time()
        at_cycle = 0
        cal_cycle = 0
        if interval == "1w":
            at_cycle = cycle1w
            cal_cycle = nt // cycle4h
        if interval == "1d":
            at_cycle = cycle1d
            cal_cycle = nt // cycle1d
        if interval == "4h":
            at_cycle = cycle4h
            cal_cycle = nt // cycle4h
        if interval == "1h":
            at_cycle = cycle1h
            cal_cycle = nt // cycle1h
        if interval == "30m":
            at_cycle = cycle30m
            cal_cycle = nt // cycle30m
        if interval == "15m":
            at_cycle = cycle15m
            cal_cycle = nt // cycle15m
        return at_cycle, cal_cycle

    def get_data(self):
        for interval in self.intervals:

            # at_cycle, cal_cycle = self.get_curr_cycle(interval)
            # if self.curr_t[interval] == "":
            #     self.read_cache_data(interval)
            #     continue

            # curr_cycle = get_timestamp(self.curr_t[interval]) // at_cycle
            # if (curr_cycle >= cal_cycle )&(self.real_time is False):
            #     self.read_cache_data(interval)
            #     continue
            # self.read_cache_data(interval)
            self.fetch_api_data(interval)

        self.tabulate()
        self.push()

        # self.tabulate()
        # if self.lazy is True:
        #     self.fetch_api_data()
        #     self.read_cache_data()
        #     return

        # data = self.read_cache_data()
        # if data != "":
        #     nt = time.time()
        #     nt.hour//self.interval[:-1]==0
        #     rt = get_timestamp(data['time'])
        #     if rt+interval>nt
        #         return
        return

    def fetch_api_data(self, interval):
        # print("debug-->fetch_api_data")
        # self.curr_t[interval] = format_time(time.time())
        # return
        # api get indicator
        # key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbHVlIjoiNjdjNjhhMmY4MDZmZjE2NTFlNjhhYjg3IiwiaWF0IjoxNzQxMDY0OTA4LCJleHAiOjMzMjQ1NTI4OTA4fQ.KmUs_BbUDc0oehhHP2Wxlr-O835IXU4YNvGnJPwe1rQ"

        res = {"time": format_time(time.time())}
        url = "https://api.taapi.io/bulk"

        payload = {
            "secret": random.choice(self.api_keys),
            "construct": [
                {
                    "exchange": "binance",
                    "symbol": "BTC/USDT",
                    "interval": interval,
                    "indicators": [
                        {"id": "price", "indicator": "price"},
                        {"id": "bbands", "indicator": "bbands"},
                        {"id": "macd", "indicator": "macd"},
                        {"id": "kdj", "indicator": "kdj"},
                        {"id": "rsi", "indicator": "rsi", "period": 6},
                        {"id": "tdsequential", "indicator": "tdsequential"},
                    ],
                }
            ],
        }
        headers = {
            "Content-Type": "application/json",
            "Accept-Encoding": "application/json",
        }

        status_code = 0
        rsp_text = ""
        try:
            response = requests.post(
                url, json=payload, headers=headers, timeout=5)
            status_code = response.status_code
            rsp_text = response.text
        except requests.exceptions.ConnectionError as e:
            print(f"Error connecting to the server: {e}")
        except requests.exceptions.HTTPError as e:
            print(f"HTTP error occurred: {e}")
        except requests.exceptions.RequestException as e:
            print(f"An error occurred: {e}")
        except Exception as e:
            print(f"other err: {e}")

        # 检查响应状态码
        if status_code != 200:
            print(f"请求失败，状态码: {status_code}")
            # 打印响应内容
            print(rsp_text)
            # retry
            time.sleep(1)
            self.fetch_api_data(interval)
            return
            # else:

        try:
            # 将响应内容解析为 Python 对象
            json_data = response.json()
            # print(f"请求成功: {json_data}")
            print("请求成功")
        except ValueError:
            print("响应内容不是有效的 JSON 格式。")
            return res
        # indicator = response
        # print(response.text)

        data = json_data["data"]
        for item in data:
            if item["indicator"] == "price":
                res["price"] = item["result"]["value"]
            if item["indicator"] == "bbands":
                res["band_up"] = item["result"]["valueUpperBand"]
                res["band_md"] = item["result"]["valueMiddleBand"]
                res["band_dn"] = item["result"]["valueLowerBand"]
            if item["indicator"] == "macd":
                res["dif"] = item["result"]["valueMACD"]
                res["dea"] = item["result"]["valueMACDSignal"]
                res["macd"] = item["result"]["valueMACDHist"]
            if item["indicator"] == "kdj":
                res["k"] = item["result"]["valueK"]
                res["d"] = item["result"]["valueD"]
                res["j"] = item["result"]["valueJ"]
            if item["indicator"] == "rsi":
                res["rsi"] = item["result"]["value"]
            if item["indicator"] == "tdsequential":
                buy_td9 = item["result"]["buySetupIndex"]
                res["buy_td9"] = buy_td9
                sell_dt9 = item["result"]["sellSetupIndex"]
                res["sell_dt9"] = sell_dt9

        self.curr_t[interval] = res["time"]
        self.datas[interval] = res
        self.write_cache_data(interval, res)

        return

    def get_signal(self, data):
        # if (data["macd"] < 0) & (data["dif"] < data["dea"]) & (data["j"] < 50) & (data["rsi"] < over_sell):
        #     return "Long"
        if (data["macd"] > 0) & (data["dif"] > data["dea"]) & (data["j"] > 50) & (data["rsi"] > over_buy):
            return "Short"
        # if (data["macd"] > 0) & (data["price"] < data["band_md"]):
        #     return "Close Long"
        if (data["macd"] < 0) & (data["price"] > data["band_md"]):
            return "Close Short"
        if (data["rsi"] < over_sell):
            return "Close Short（RSI）"
        if (data["rsi"] > over_buy):
            return "Open Short"
        # if (data["rsi"] < over_sell):
        #     return "Close Short | Open Long"
        # if (data["rsi"] > over_buy):
        #     return "Close Long | Open Short"

    def tabulate(self):
        # formatted_time = format_time(time.time())

        red = "🟥"
        green = "🟩"
        fire = "🔥"
        normal = ""
        ice = "🧊"

        curr = "--->"
        top = {
            "Time": [self.curr_t[self.intervals[-1]]],
            "Symbol": [self.symbol],
        }

        price = self.datas[self.intervals[-1]]["price"]
        mid = {
            curr: [price],
        }
        s = copy.deepcopy(self.stress)
        mid = {**mid, **s}

        tend = {}
        mjr = {}
        for interval, data in self.datas.items():
            # data = self.datas[interval]
            mid[f"{interval} BOLL UP"] = [data["band_up"], "3%"]
            mid[f"{interval} BOLL MD"] = [data["band_md"], "2%"]
            mid[f"{interval} BOLL DN"] = [data["band_dn"], "1%"]

            tend[f"{interval}"] = [f"{red if data['macd'] > 0 else green}"]

            mjr[f"{interval} MACD"] = [
                f"DIF:{data['dif']:.1f} DEA:{data['dea']:.1f} MACD:{data['macd']:.1f}"
            ]
            mjr[f"{interval} KDJ"] = [f"D:{data['d']:.1f} J:{data['j']:.1f}"]
            mjr[f"{interval} RSI"] = [
                f"{data['rsi']:.1f} {fire if data['rsi'] > over_buy else ice if data['rsi'] < over_sell else normal}"
            ]
            mjr[f"{interval} buy_td9"] = [data['buy_td9']]
            mjr[f"{interval} sell_dt9"] = [data['sell_dt9']]
            mjr[f"{interval} Suggest"] = [f"{self.get_signal(data)}"]

        del_keys = []
        for k, v in mid.items():
            if k == curr:
                continue

            p = short_profit(v[0], price)
            if abs(p) > 0.15:
                del_keys.append(k)
                # del mid[k]
                continue
            mid[k][1] = f"{p * 1e2:.2f}%"
            mid[k][0] = f"{v[0]:.1f}"

        for k in del_keys:
            del mid[k]

        mid[curr][0] = f"{price:.1f}"
        sorted_mid = dict(sorted(
            mid.items(), key=lambda x: float(x[1][0]), reverse=True))

        self.out = {**top, **sorted_mid, **tend, **mjr}
        return
        # df = pd.DataFrame(data=out.values(), index=out.keys(), columns=["Price", "Compare"])
        # left_aligned_df = df.style.set_properties(**{"text-align": "left"})
        # left_aligned_df

    def push(self):
        out = self.out
        df = pd.DataFrame(
            data=out.values(), index=out.keys(), columns=["Price", "Compare"]
        )
        dfto = df.to_markdown(
            tablefmt="simple",
            colalign=("right",),
            stralign="left",
        )
        # dfto = dfto.replace("  ", "    ")  # 替换所有空格
        print(dfto)

        need_push = False
        for interval in self.intervals:
            need_push = (out[f'{interval} Suggest'][0] != "None")
            if need_push:
                # pass
                try:
                    bark = BarkNotificator(device_token=self.push_key)
                    bark.send(title="", content=dfto, ringtone='update.caf', call='1')
                except Exception as e:
                    print(f'An exception occurred:{e}')
                return
        # pass


if __name__ == "__main__":
    # start as 22:10 stop at 5:00
    # hour = datetime.fromtimestamp(time.time()).hour
    # freq=30*60
    # if (hour>=22)|(hour<=5):
    #     freq=2*60
    # 打印脚本名称
    print(f"脚本名称: {sys.argv[0]}")

    # 打印所有传递的参数
    if len(sys.argv) > 1:
        freq = sys.argv[1]

    api_keys = [
        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbHVlIjoiNjdjNjhhMmY4MDZmZjE2NTFlNjhhYjg3IiwiaWF0IjoxNzQxMDY0OTA4LCJleHAiOjMzMjQ1NTI4OTA4fQ.KmUs_BbUDc0oehhHP2Wxlr-O835IXU4YNvGnJPwe1rQ",
        # below from temp mail
        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbHVlIjoiNjdjNzNjMjQ4MDZmZjE2NTFlODQzMWIzIiwiaWF0IjoxNzQxMTEwMzMwLCJleHAiOjMzMjQ1NTc0MzMwfQ.RH8ILVUHQLOpOIv4M8xJromc3ZpQH1OB6ENXoe48uXc",
        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbHVlIjoiNjdjNzNjZmU4MDZmZjE2NTFlODQ1M2EyIiwiaWF0IjoxNzQxMTEwNTM5LCJleHAiOjMzMjQ1NTc0NTM5fQ.jDk13U6kk0-Y4WM6KIqFenH3p3giv6TqaKGpMcCUtfw",
        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbHVlIjoiNjdjN2I2NWY4MDZmZjE2NTFlOTcxZDYwIiwiaWF0IjoxNzQxMTQxNjE1LCJleHAiOjMzMjQ1NjA1NjE1fQ.xreTK-ZkZPvtDn5QvPHBLlhvF7k7YZ2Fbj71rwNlGoo",
        "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbHVlIjoiNjdjN2I2YWI4MDZmZjE2NTFlOTcyOTA4IiwiaWF0IjoxNzQxMTQxNjg4LCJleHAiOjMzMjQ1NjA1Njg4fQ.bln2pj4_AyR6O_PlR9RzOPBz3F3mPjfEY8_x7fyKJ7k"
    ]
    push_key = "5e926333c83d7bb5c9e65e2550ed9684e7d1357daa0f8d7cf865b57439c0aed0"
    intervals = ["4h", "1h", "30m"]
    # intervals = ["1w", "1d", "4h"]
    watcher = MjrWatcher(api_keys, push_key,
                         intervals=intervals, real_time=True)
    watcher.get_data()

    # schedule.every().day.at("22:30").do(watcher.get_data)
    # Run job every hour at the 30nd minute
    schedule.every().hour.at(":00").do(watcher.get_data)
    schedule.every().hour.at(":30").do(watcher.get_data)

    # start as 22: 10 stop at 5: 00
    # schedule.every(30).minutes.do(watcher.get_data)
    # schedule.every(30).seconds.do(watcher.get_data)
    while True:
        schedule.run_pending()
        time.sleep(1)
        # time.sleep(5)
    # watcher.tabulate()
    # watcher.push()
# loop 30s // timenow//30 //1h //4h


# my api_key = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbHVlIjoiNjdjNjhhMmY4MDZmZjE2NTFlNjhhYjg3IiwiaWF0IjoxNzQxMDY0OTA4LCJleHAiOjMzMjQ1NTI4OTA4fQ.KmUs_BbUDc0oehhHP2Wxlr-O835IXU4YNvGnJPwe1rQ"
# tmp_mail:
